// 引入默认比较函数和交换函数
// defaultCompare(a, b): 比较函数，返回 1, 0 或 -1
// swap(array, i, j): 交换数组 array 中下标 i 和 j 位置的元素
const { defaultCompare, swap } = require("./utils.js");

/**
 * 将给定节点 (index) 以及它的左右子节点维护在最大堆的状态。
 *
 * @param {Array} array - 待维护堆的数组
 * @param {number} index - 当前要维护堆性质的节点下标
 * @param {number} heapSize - 当前堆中有效元素的数量（或边界）
 * @param {Function} compareFn - 比较函数，用于比较两个元素大小
 */
function heapify(array, index, heapSize, compareFn) {
  let largest = index; // 先假设当前的节点就是最大节点的索引

  // 根据当前节点的索引计算左子节点和右子节点的下标
  const left = 2 * index + 1;
  const right = 2 * index + 2;

  // 接下来就需要和左右的子节点进行比较，如果比左右的子节点小，那么就要更新 largest

  // 如果左子节点存在，并且左子节点的值比当前的节点值大，就更新 largest
  if (left < heapSize && compareFn(array[left], array[index]) > 0) {
    largest = left;
  }

  // 如果右子节点存在，并且右子节点的值比当前的节点值大，就更新 largest
  if (right < heapSize && compareFn(array[right], array[largest]) > 0) {
    largest = right;
  }

  if (largest !== index) {
    // 如果进入此分支，说明 largest 被更新过，也就是说，子节点中有更大的值
    swap(array, index, largest);
    // 接下来继续递归的进行调整
    heapify(array, largest, heapSize, compareFn);
  }
}

/**
 * 构建最大堆。
 *
 * 原理：从最后一个非叶子节点开始，向前（即从底部到顶部）逐个调用 heapify，
 *       这样能保证堆的所有子树都满足最大堆的性质。
 *
 * @param {Array} array - 待构建最大堆的数组
 * @param {Function} compareFn - 比较函数
 * @returns {Array} 构建好的最大堆（实际上仍旧是传入的那个数组）
 * 0~ Math.floor(array.length / 2)
 */
function buildMaxHeap(array, compareFn) {
  // 从最后一个非叶子节点开始，调整结构，使其成为一个最大堆
  for (let i = Math.floor(array.length / 2); i >= 0; i--) {
    heapify(array, i, array.length, compareFn);
  }
}

/**
 * 堆排序函数。
 * 1. 先构建一个最大堆
 * 2. 不断将堆顶元素（最大值）和末尾元素互换
 * 3. 交换后减小堆的范围，重新维护堆顶，以保持最大堆性质
 *
 * 时间复杂度：
 * - 构建最大堆 O(n)
 * - 交换并维护堆 O(n log n)
 *
 * @param {Array} array - 待排序的数组
 * @param {Function} compareFn - 比较函数，默认为 defaultCompare
 * @returns {Array} - 已完成排序的数组（原地排序）
 */
function heapSort(array, compareFn = defaultCompare) {
  let heapSize = array.length; // 获取堆的大小
  // 构建最大堆
  buildMaxHeap(array, compareFn);

  // 目前就已经形成了最大堆 [5, 3, 4, 1, 2]
  // 形成的最大堆，能够保证的是数组的第一个一定是最大的
  while (heapSize > 1) {
    // 1. 交换堆顶元素（数组第一个元素）和数组末尾元素
    swap(array, 0, --heapSize); // [2, 3, 4, 1, 5]
    // 2. 缩小堆的范围，重新形成最大堆
    // 注意，这里在形成新的最大堆结构的时候，最大堆的范围就已经缩小了
    // 也就是说，目前是针对 [2, 3, 4, 1] 这几个元素来形成新的最大堆
    heapify(array, 0, heapSize, compareFn);
  }
  return array;
}

const array = [36, 27, 20, 60, 55, 7, 28, 39, 67, 44, 16];
const sortedArray = heapSort(array);
console.log("排序后:", sortedArray);
